package com.house365.im.client;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;

import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.PacketCollector;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Registration;
import org.jivesoftware.smack.packet.XMPPError;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Handler;
import android.util.Log;

import com.house365.im.client.listener.MessagePacketListener;
import com.house365.im.client.listener.PersistentConnectionListener;
import com.house365.im.client.util.LogUtil;

/**
 * This class is to manage the XMPP connection between client and server.
 * 
 * @modifier Rick Ping
 */
public class XmppManager {

	private static final String LOGTAG = LogUtil.makeLogTag(XmppManager.class);

	private static final String XMPP_RESOURCE_NAME = "house365";

	private Context context;

	private IMManager.TaskSubmitter taskSubmitter;


	private SharedPreferences sharedPrefs;

	private String xmppHost;

	private int xmppPort;

	private XMPPConnection connection;

	private ConnectionListener connectionListener;

	private PacketListener messagePacketListener;

	private Handler handler;

	private List<Runnable> taskList;

	private boolean running = false;

	private Future<?> futureTask;

	private IMManager imManager;


	public XmppManager(IMManager imManager) {
		this.imManager = imManager;
		context = imManager.getContext();
		taskSubmitter = imManager.getTaskSubmitter();
		sharedPrefs = imManager.getSharedPrefs();

		xmppHost = sharedPrefs.getString(Constants.XMPP_HOST, "localhost");
		xmppPort = sharedPrefs.getInt(Constants.XMPP_PORT, 5222);

		connectionListener = new PersistentConnectionListener(this);
		messagePacketListener = new MessagePacketListener(this);

		handler = new Handler();
		taskList = new ArrayList<Runnable>();
	}

	public Handler getHandler() {
		return handler;
	}

	public void setHandler(Handler handler) {
		this.handler = handler;
	}

	public Context getContext() {
		return context;
	}

	



	public void terminatePersistentConnection() {
		Log.d(LOGTAG, "terminatePersistentConnection()...");
		Runnable runnable = new Runnable() {

			final XmppManager xmppManager = XmppManager.this;

			public void run() {
				if (xmppManager.isConnected()) {
					Log.d(LOGTAG, "terminatePersistentConnection()... run()");
					xmppManager.getConnection().removePacketListener(
							xmppManager.getMessagePacketListener());
					xmppManager.getConnection().disconnect();
				}
				xmppManager.runTask();
			}

		};
		addTask(runnable);
	}

	public XMPPConnection getConnection() {
		return connection;
	}

	public void setConnection(XMPPConnection connection) {
		this.connection = connection;
	}

	public ConnectionListener getConnectionListener() {
		return connectionListener;
	}

	
	

	public PacketListener getMessagePacketListener() {
		return messagePacketListener;
	}

	public void setMessagePacketListener(PacketListener messagePacketListener) {
		this.messagePacketListener = messagePacketListener;
	}

	public List<Runnable> getTaskList() {
		return taskList;
	}

	public Future<?> getFutureTask() {
		return futureTask;
	}

	public void runTask() {
		synchronized (taskList) {
			running = false;
			futureTask = null;
			if (!taskList.isEmpty()) {
				Runnable runnable = (Runnable) taskList.get(0);
				taskList.remove(0);
				running = true;
				futureTask = taskSubmitter.submit(runnable);

				if (futureTask == null) {
					try {
						futureTask.get();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

	public boolean isConnected() {
		return connection != null && connection.isConnected();
	}

	public boolean isAuthenticated() {
		return connection != null && connection.isConnected()
				&& connection.isAuthenticated();
	}

	

	private void addTask(Runnable runnable) {
		synchronized (taskList) {
			if (taskList.isEmpty() && !running) {
				running = true;
				futureTask = taskSubmitter.submit(runnable);
			} else {
				// add by rick,resolve can not re-connect to server after server
				// restart
				runTask();
				taskList.add(runnable);
			}
		}
	}



	public int ConnectTask(boolean force) {
		Log.i(LOGTAG, "ConnectTask.run()...");

		if (force || !isConnected()) {
			// first remove account as net work is reconnect
			// removeAccount();
			// Create the configuration for this new connection
			ConnectionConfiguration connConfig = new ConnectionConfiguration(
					xmppHost, xmppPort);
			// connConfig.setSecurityMode(SecurityMode.disabled);
			connConfig.setSecurityMode(SecurityMode.disabled);
			connConfig.setSASLAuthenticationEnabled(true);
			connConfig.setCompressionEnabled(false);
			connConfig.setReconnectionAllowed(true);
			connConfig.setSendPresence(true);
			XMPPConnection connection = new XMPPConnection(connConfig);
			setConnection(connection);

			try {
				// Connect to the server
				connection.connect();
				Log.i(LOGTAG, "XMPP connected successfully");
				// packet provider
//				ProviderManager.getInstance().addIQProvider("notification",
//						"androidpn:iq:notification",
//						new NotificationIQProvider());

			} catch (Exception e) {
				Log.e(LOGTAG, "XMPP connection failed", e);
				return IMManager.CONN_UNAVLIABLE;
			}
			return IMManager.CONN_SUCCESS;

		} else {
			Log.i(LOGTAG, "XMPP connected already");
			return IMManager.CONN_ALREAY;
		}
	}

	public void RegisterTask(final String username,final String password) {
			try {
				Registration registration = new Registration();

				PacketFilter packetFilter = new AndFilter(new PacketIDFilter(
						registration.getPacketID()), new PacketTypeFilter(
						IQ.class));

				PacketListener packetListener = new PacketListener() {

					public void processPacket(Packet packet) {
						Log.d("RegisterTask.PacketListener",
								"processPacket().....");
						Log.d("RegisterTask.PacketListener",
								"packet=" + packet.toXML());

						if (packet instanceof IQ) {
							IQ response = (IQ) packet;
							if (response.getType() == IQ.Type.ERROR) {
								if (!response.getError().toString()
										.contains("409")) {
									Log.e(LOGTAG,
											"Unknown error while registering XMPP account! "
													+ response.getError()
															.getCondition());
								}
							} else if (response.getType() == IQ.Type.RESULT) {

								Editor editor = sharedPrefs.edit();
								editor.putString(Constants.XMPP_USERNAME,
										username);
								editor.putString(Constants.XMPP_PASSWORD,
										password);
								editor.commit();
								Log.i(LOGTAG, "Account registered successfully");
							}
						}
					}
				};

				connection.addPacketListener(packetListener, packetFilter);

				registration.setType(IQ.Type.SET);
				registration.addAttribute("username", username);
				registration
						.addAttribute("password", password);
				connection.sendPacket(registration);
			} catch (Exception e) {
				Log.e(LOGTAG, "RegisterTask.run()... other error");
				// xmppManager.startReconnectionThread();
			}
		}

	public int LoginTask(boolean force,final String username,final String password) {

		Log.i(LOGTAG, "LoginTask.run()...");

		if (force || !isAuthenticated()) {

			try {
				getConnection().login(username, password,NamespaceC.XMPP_RESOURCE_NAME);
				Log.i(LOGTAG, "Loggedn in successfully");

				// connection listener
				if (getConnectionListener() != null) {
					getConnection().addConnectionListener(
							getConnectionListener());
				}

				// packet filter
				PacketFilter packetFilter = new PacketTypeFilter(
						Message.class);
				// packet listener
				connection.addPacketListener(getMessagePacketListener(), packetFilter);
				return IMManager.AUTH_SUCCESS;
			} catch (XMPPException e) {
				Log.e(LOGTAG, "LoginTask.run()... xmpp error");
				Log.e(LOGTAG,
						"Failed to login to xmpp server. Caused by: "
								+ e.getMessage());
				if(e.getXMPPError() == null){
					return IMManager.CONN_UNAVLIABLE; 
				}
				else if(XMPPError.Condition.not_allowed.toString().equals(e.getXMPPError().getCondition())){
					//password is wrong
					return IMManager.AUTH_USERNAME_INVLIDATE; 
				}else if(XMPPError.Condition.not_authorized.toString().equals(e.getXMPPError().getCondition())){
					//password is wrong
					return IMManager.AUTH_USERNAME_INVLIDATE; 
				}else if(XMPPError.Condition.forbidden.toString().equals(e.getXMPPError().getCondition())){
					//password is wrong
					return IMManager.AUTH_PASSWORD_INVLIDATE; 
				}
				return IMManager.AUTH_USERNAME_INVLIDATE; 

			} catch (Exception e) {
				Log.e(LOGTAG, "LoginTask.run()... other error");
				Log.e(LOGTAG,
						"Failed to login to xmpp server. Caused by: "
								+ e.getMessage());
				// xmppManager.startReconnectionThread();
				return IMManager.CONN_UNAVLIABLE; 
			}

		} else {
			Log.i(LOGTAG, "Logged in already");
			return IMManager.AUTH_ALREAY;
		}

	}

	/**
	 * send the package of heart beat
	 */
	public boolean sendHeartBeatPkg() {
		if (isAuthenticated()) {
			Presence p = new Presence(Presence.Type.available);
			p.addExtension(new PacketExtension() {
				
				@Override
				public String toXML() {
					// TODO Auto-generated method stub
				return "<os namespace=\"android\"/>";
				}
				
				@Override
				public String getNamespace() {
					// TODO Auto-generated method stub
					return "android";
				}
				
				@Override
				public String getElementName() {
					// TODO Auto-generated method stub
					return "os";
				}
			});
			try {
				PacketCollector collector =getConnection().createPacketCollector(new PacketIDFilter(p.getPacketID()));
				getConnection().sendPacket(p);
				Presence r = (Presence)collector.nextResult(2000);
				if (r != null) {
					return true;
				} 
				return false;
			} catch (Exception e) {
				Log.e(LOGTAG, e.getMessage());
				return false;
			}
		}else{
			return false;
		}

	}

	public IMManager getImManager() {
		return imManager;
	}

	public void setImManager(IMManager imManager) {
		this.imManager = imManager;
	}
	
	
}
